#ifndef __TCP_SERVER_HPP__
#define __TCP_SERVER_HPP__

#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <csignal>
#include <functional>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <fstream>

#include "Log.hpp"
#include "Unit.hpp"

namespace qihai
{
    class TcpServer;
    struct ClientInfo;

    static const uint32_t RECVBUFFERLEN = 1024;// 一次接收用户缓冲区大小4
    // using Func_t = std::function<void(TcpServer*, ClientInfo*)>;  // 设置类型 使用C++的包装器封装  类型需要提前声明
    typedef void(*Func_t)(TcpServer*, ClientInfo*);  // 使用typedef 给函数指针取别名也行
    static const char* NULLFILE = "/dev/null";  // 默认重定向的空文件
    static const char* TCPSERVERCONF = "./qihai_tcpserver/conf/tcpserver.conf";

    // 客户端信息
    struct ClientInfo
    {
        int _clientSocket;  // 服务套接字
        std::string _clientIp;  // 客户端ip
        uint16_t _clientPort;  // 客户端端口
    };

    // 设置服务器为守护进程
    inline static bool SetDaemon(const std::string& out = NULLFILE, const std::string& in = NULLFILE, const std::string& err = NULLFILE)
    {
        // 设置log文件为守护进程打印模式
        LOG_ISDAEMON = true;

        // 对两个信号进行忽略，防止出现内存泄漏和僵尸进程
        signal(SIGPIPE, SIG_IGN);  // 防止对关闭的客户端发送消息产生的信号被操作系统杀掉
        signal(SIGCHLD, SIG_IGN);  // 防止出现僵尸进程防止内存泄漏

        // 首先自己需要得到1号进程托管 -> 1号进程为自己的父进程 - > 成为孤儿进程即可
        if (0 < fork()) exit(0);  // 父进程退出
        // 孤儿进程
        // 自成一个会话脱离当前会话环境（防止当前会话比如bash退出会波及自己）
        if (0 > setsid())
        {
            LOG(ERROR) << "create a new session error...." << std::endl;
            return false;
        }
        // 此时是守护进程了，此时要将默认打开的三个文件流进行重定向 防止影响正常的前台程序
        int nullFile = open(NULLFILE, O_RDONLY | O_WRONLY);  // 打开和写入
        int outFile, inFile, errFile;
        outFile = inFile = errFile = nullFile;

        // 下面可能涉及创建文件操作
        if (out != NULLFILE) outFile = open(out.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);  // 别忘记了是追加写还是写入时清空
        if (in != NULLFILE) inFile = open(in.c_str(), O_RDONLY | O_CREAT, 0666);
        if (err != NULLFILE) errFile = open(err.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (outFile < 0 || inFile < 0 || errFile < 0)
        {
            LOG(ERROR) << "Failed to open the file before dup2....." << std::endl;  // 测试前打开文件失败
            return false;
        }

        // 默认打开的是stdin、stdout、stderr 默认输入，默认输出，默认错误(键盘读取、输出到屏幕上、输出到屏幕上)
        // 文件1 <-重定向- 文件2
        int check1 = dup2(inFile, 0);
        int check2 = dup2(outFile, 1);
        int check3 = dup2(errFile, 2);
        if (check1 < 0 || check2 < 0 || check3 < 0)
        {
            LOG(ERROR) << "dup2 error......" << std::endl;  // 重定向失败，检查错误
            return false;
        }


        // 关闭文件
        close(nullFile);
        if (out != NULLFILE) close(outFile);
        if (in != NULLFILE) close(inFile);
        if (err != NULLFILE) close(errFile);
        return true;
    }

    class TcpServer
    {
    public:
        TcpServer(TcpServer&) = delete;
        // 初始化TCP服务器：创建监听套接字，绑定IP和端口，设置连接队列长度
        // 默认ip为"0.0.0.0" 连接队列为20
        // 注意，v1.2版本下初始化参数(port、ip、listenLen)均已失效，读取是当前目录下的conf/tcpserver.conf文件 初始化服务器的设置在那里进行
        TcpServer(uint16_t port = 8080, std::string ip = "0.0.0.0", int listenLen = 20)
            :_serverPort(port), _serverIp(ip), _listenLen(listenLen),  _listenSocket(-1), _isDaemon(false)
        {
            if (!LoadServerConf(TCPSERVERCONF)) LOG(WARNING) << "If an error exists in the configuration file, use the default configuration......" << std::endl;

            // 是否设置为守护进程 -当前只是将日志文件的输出重定向到当前目录下：tcpserver.log
            if (_isDaemon) SetDaemon("tcpserver.log");

            // 1.创建套接字
            _listenSocket = socket(AF_INET, SOCK_STREAM, 0);  // AF_INET IPV4 SOCK_STREAM 流式-字节流(TCP) 0自动推导
            if (_listenSocket < 0)
            {
                // 差错处理
                LOG(FATAL) << "socket error..." << std::endl;
                exit(errno);
            }

            // 2.bind 绑定ip和port
            // 绑定之前需要用sock网络结构体保存ip、port等信息，并且转换为网络字节序
            struct sockaddr_in server;
            memset(&server, 0, sizeof server);
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverIp.c_str());
            server.sin_port = htons(_serverPort);
            if (0 > bind(_listenSocket, (sockaddr*)&server, sizeof server))
            {
                // 差错处理
                LOG(FATAL) << "bind error..." << std::endl;
                exit(errno);
            }

            // 3.设置监听套接字 确定TCP协议底下全连接队列的大小
            if (0 > listen(_listenSocket, _listenLen))
            {
                // 差错处理
                LOG(FATAL) << "listen error..." << std::endl;
                exit(errno);
            }
            LOG(DEBUG) << "TCPServer init successfully..." << std::endl;
        }

        ~TcpServer()
        {
            if (_listenSocket > 0) close(_listenSocket);
        }

        // 给对应TCP客户端发送信息
        // 注意发送套接字如果设置O_NONBLOCK是非阻塞发送，那么如果底层发送缓冲区没有空间，此次数据将会丢失-注意保存，否则会阻塞等到存在空间发送
        static bool Send(int clientSockt, const std::string& message)
        {
            ssize_t sendLen = send(clientSockt, message.c_str(), message.size(), 0);
            if (sendLen < 0)
            {
                // 差错处理
                LOG(WARNING) << "send lose......" << std::endl;
                return false;
            }
            return true;
        }

        // 接收对应TCP客户端发送的信息
        // 注意接收套接字如果设置O_NONBLOCK非阻塞接收，那么底层接收缓冲区没有数据也会返回
        // 注意存在对方关闭连接。如果对方关闭连接，设置了默认关闭则此函数会关闭连接。
        // 接收失败和关闭连接均会返回空串
        // 对方关闭连接设置isClose为true
        std::string Recv(int clientSockt, bool& isClose, bool defaultClose = true)
        {
            std::string message;

            ssize_t n = recv(clientSockt, recvBuffers, RECVBUFFERLEN - 1, 0);
            if (n < 0)
            {
                // 接收失败
                LOG(WARNING) << "recv lose....." << std::endl; 
            }
            else if (n == 0)
            {
                // 客户端关闭了连接
                isClose = true;
                if (defaultClose) close(clientSockt);
                LOG(DEBUG) << "client close link......" << std::endl; 
            }
            else{
                recvBuffers[n] = '\0';
                message = recvBuffers;
            }
            return message;
        }

        // v1.0 接收单个任务处理 func 是函数类型
        void Start(Func_t func)
        {
            // 信号捕捉函数
            signal(SIGCHLD, SIG_IGN);  // 忽略子进程的退出 防止子进程成为僵尸进程
            //进程版本 主进程提取服务套接字，子进程接收服务套接字单个进程处理
            ClientInfo clientInfo;
            while(true)
            {
                clientInfo._clientSocket = Accept(&clientInfo._clientIp, &clientInfo._clientPort);
                if (clientInfo._clientSocket < 0) continue;

                if (0 == fork())
                {
                    // 子进程
                    close(_listenSocket);
                    func(this, &clientInfo);  // 执行任务
                    exit(0);  // 退出进程
                }
                close(clientInfo._clientSocket);
            }
        }

    
    private:
        // 加载服务器配置v1.2
        bool LoadServerConf(const std::string& filePath)
        {
            // 首先利用C++接口进行读入
            std::ifstream in(filePath);
            if (!in.is_open())
            {
                // 配置文件打开失败，检查配置文件或者配置文件路径！
                LOG(ERROR) << "Configuration file failed to open, check the configuration file or configuration file path!" << std::endl;
                return false;
            }

            std::string str;
            while(std::getline(in, str))
            {
                // 每行进行读入
                if (str == "" || str[0] == '#') continue;  // 空行和注释行跳过

                // 切分字符串"="
                std::vector<std::string> strs;
                qihai::StreamProcess::SlicedString(str, "=", strs, true);  // 需要压缩

                if (strs[0] == "ip") _serverIp = strs[1];
                else if (strs[0] == "port") _serverPort = atoi(strs[1].c_str());
                else if (strs[0] == "listenLen") _listenLen = atoi(strs[1].c_str());
                else if (strs[0] == "isDaemon")
                {
                    if (strs[1] == "true" || strs[1] == "True") _isDaemon = true;
                }
                else LOG(WARNING) << "The unknown configuration item: "  << strs[0] << std::endl;  // 未知配置项
            }
            LOG(INFO) << "tcpserver.conf loaded successfully......" << std::endl;
            return true;
        }

        // 返回连接队列中的一个服务套接字
        // 建立与客户端的连接
        // 注意监听套接字需要设置O_NONBLOCK才能进行非阻塞监听，否则为阻塞监听
        // 输出参数：客户端ip和port
        // 成功返回服务套接字，失败返回-1
        int Accept(std::string* clientIp = nullptr, uint16_t* clientPort = nullptr)
        {
            struct sockaddr_in client;
            socklen_t clientLen = sizeof client;
            int clientSockt = accept(_listenSocket, (sockaddr*)&client, &clientLen);
            if (clientSockt < 0)
            {
                // 差错处理
                // 注意是否阻塞还是非阻塞
                // 当前是非阻塞
                LOG(ERROR) << "accept error..." << std::endl;
                return clientSockt;
            }

            if (clientIp) *clientIp = inet_ntoa(client.sin_addr);  // 网络字节序转换为本地字节序
            if (clientPort) *clientPort = ntohs(client.sin_port);
            LOG(DEBUG) << "accept new link......" << std::endl;
            return clientSockt;
        }
    private:
        int _listenSocket;  // 监听套接字
        uint16_t _serverPort;  // 服务器的端口
        std::string _serverIp;  // 服务器的ip
        int _listenLen;  // 全连接队列最大数额
        bool _isDaemon;  // 是否守护进程
        char* recvBuffers = new char[RECVBUFFERLEN];  // 用户接收缓冲区大小
    };
}



#endif